/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com */

/*
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef GL_FONT_H_
#define GL_FONT_H_

#include "FreeType.h"
#include <iostream>
#include <vector>
#include <string>
#include "base/logging.h"

using namespace std;

namespace tri {

class triCharData {
public:
	triCharData() :
		mTexture(0), mBitmapWidth(0), mBitmapRows(0), mBitmapGlyphLeft(0),
				mBitmapGlyphTop(0), mGlyphAdvanceX(0), mWidth(0), mHeight(0) {

	}

	virtual ~triCharData() {
		glDeleteTextures(1, &mTexture);
	}

	GLuint mTexture;
	int mBitmapWidth;
	int mBitmapRows;
	int mBitmapGlyphLeft;
	int mBitmapGlyphTop;
	int mGlyphAdvanceX;
	int mWidth;
	int mHeight;
};

class triFont;

///This function gets the first power of 2 >= the
///int that we pass it.
inline int next_p2(int a) {
	int rval=1;
	while (rval<a)
		rval<<=1;
	return rval;
}

/**
 * Get the font object
 */
inline void loadFT_Face(FT_Library& library, FT_Face& face, const char * fname,
		unsigned int h) {

	//This is where we load in the font information from the file.
	//Of all the places where the code might die, this is the most likely,
	//as FT_New_Face will die if the font file does not exist or is somehow broken.
	if (FT_New_Face(library, fname, 0, &face ))
		throw tri::TriException("FT_New_Face failed (there is probably a problem with your font file)");

	//For some twisted reason, Freetype measures font size
	//in terms of 1/64ths of pixels.  Thus, to make a font
	//h pixels high, we need to request a size of h*64.
	//(h << 6 is just a prettier way of writting h*64)
	FT_Set_Char_Size(face, h << 6, h << 6, 96, 96);
}

inline void destroyFT_Face(FT_Face& face) {
	//We don't need the face information now that the display
	//lists have been created, so we free the assosiated resources.
	FT_Done_Face(face);

}

inline void createGlyphtexture(triCharData* data, FT_Bitmap& bitmap) {
	//Use our helper function to get the widths of
	//the bitmap data that we will need in order to create
	//our texture.
	int width = next_p2(data->mBitmapWidth);
	int height = next_p2(data->mBitmapRows);

	data->mWidth = width;
	data->mHeight = height;

	//Allocate memory for the texture data.
	//GLubyte* expanded_data = new GLubyte[ 2 * width * height];

	//Here we fill in the data for the expanded bitmap.
	//Notice that we are using two channel bitmap (one for
	//luminocity and one for alpha), but we assign
	//both luminocity and alpha to the value that we
	//find in the FreeType bitmap. 
	//We use the ?: operator so that value which we use
	//will be 0 if we are in the padding zone, and whatever
	//is the the Freetype bitmap otherwise.
	/*for (int j=0; j <height; j++) {
		for (int i=0; i < width; i++) {
			expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] =(i>=bitmap.width|| j>=bitmap.rows) ? 0
					: bitmap.buffer[i + bitmap.width*j];
		}
	}*/
	GLubyte* expanded_data = new GLubyte[ 4 * width * height];

	for (int j=0; j <height; j++) {
		for (int i=0; i < width; i++) {
			expanded_data[4*(i+j*width)]= 255;
			expanded_data[4*(i+j*width)+1]= 255;
			expanded_data[4*(i+j*width)+2]= 255;
			expanded_data[4*(i+j*width)+3]=(i>=bitmap.width|| j>=bitmap.rows) ? 0
					: bitmap.buffer[i + bitmap.width*j];
		}
	}

	glGenTextures( 1, &data->mTexture);

	//Now we just setup some texture paramaters.
	glBindTexture(GL_TEXTURE_2D, data->mTexture);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	//Here we actually create the texture itself, notice
	//that we are using GL_LUMINANCE_ALPHA to indicate that
	//we are using 2 channel data.
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
	//GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data);
	
	//gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA, width, height,
	//GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data);
	gluBuild2DMipmaps(GL_TEXTURE_2D,4, width, height,
		GL_RGBA, GL_UNSIGNED_BYTE, expanded_data);

	//With the texture created, we don't need to expanded data anymore
	delete [] expanded_data;

}

inline void createCharacter(FT_Face face, char ch, triCharData* data) {
	//The first thing we do is get FreeType to render our character
	//into a bitmap.  This actually requires a couple of FreeType commands:

	//Load the Glyph for our character.
	if (FT_Load_Glyph(face, FT_Get_Char_Index(face, ch ), FT_LOAD_DEFAULT ))
		throw tri::TriException("FT_Load_Glyph failed");

	//Move the face's glyph into a Glyph object.
	FT_Glyph glyph;
	if (FT_Get_Glyph(face->glyph, &glyph ))
		throw tri::TriException("FT_Get_Glyph failed");

	//Convert the glyph to a bitmap.
	FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1);
	FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

	//This reference will make accessing the bitmap easier
	FT_Bitmap& bitmap=bitmap_glyph->bitmap;

	data->mBitmapWidth = bitmap.width;
	data->mBitmapRows = bitmap.rows;
	data->mBitmapGlyphLeft = bitmap_glyph->left;
	data->mBitmapGlyphTop = bitmap_glyph->top;
	data->mGlyphAdvanceX = face->glyph->advance.x;

	createGlyphtexture(data, bitmap);

}

class triFont {
public:
	triFont() :
		mCharHeight(0) {

	}

	virtual ~triFont() {

	}

	static inline triFont* getFont(std::string font, unsigned int size) {
	    if(font == ""){
			return NULL;
		}
		LOG_INFO(std::string("Loading Font....")+font+" size:"+tri::utils::int2String((int)size));
		triFont* retFont = new triFont();

		//Create and initilize a freetype font library.
		FT_Library library;
		if (FT_Init_FreeType( &library )) {
			throw tri::TriException("FT_Init_FreeType failed");
		}
		//			throw std::runtime_error("FT_Init_FreeType failed");

		//	//The object in which Freetype holds information on a given
		//	//font is called a "face".
		FT_Face face;
		loadFT_Face(library, face, font.c_str(), size);

		for (unsigned char ch=0; ch<128; ch++) {
			//Create Characters
			createCharacter(face, ch, &retFont->mCharacters[(int)ch]);
		}
		char fChar = 'l';
		retFont->mCharHeight = retFont->mCharacters[(int)fChar].mBitmapRows;
		////std::cout<<(char)ch<<" "<<retFont->mCharacters[(int)ch].mTexture;

		destroyFT_Face(face);

		//Ditto for the library.
		FT_Done_FreeType(library);

		return retFont;
	}
	
	inline float getGlyphTopMargin( float height=1){
		char fChar = 'g'; 
		triCharData*  data  = &mCharacters[(int)fChar];
		float scale = height/mCharHeight;
		return -1*( (data->mBitmapGlyphTop-data->mBitmapRows)*scale );
	} 

	/**
	 * Get the lenght of a string using the current font.
	 */
	inline void getDimensions(std::string t, float rHeight, float &width,
			float &height) {
		char ch = ' ';
		float scale = rHeight/mCharHeight;
		width = 0.0;
		height = rHeight;
		triCharData* data=NULL;
		for (unsigned int c = 0; c<t.size(); c++) {
			if (data!=NULL) {
				width+=(data->mGlyphAdvanceX >> 6)*scale;
			}
			ch = t.c_str()[c];
			data = &mCharacters[(int)ch];
			if (t.size()-1==c) {
				width+=data->mBitmapWidth*scale;
			}
		}

	}

	/**
	 * Get the lenght of a string using the current font.
	 */
	inline void getDimensions(std::vector<std::string>& t, float rHeight,
			float &width, float &height) {
		char ch = ' ';
		float scale = rHeight/mCharHeight;
		width = 0.0;
		float w=0,h=0;
		triCharData* data=NULL;
		for (int c = 0; c<t.size(); c++) {
			getDimensions(t[c], rHeight, w, h);
			if (w>width) {
				width = w;
			}
		}
		height = rHeight*t.size()*1.5;
	}

	inline void print_char(triCharData* data, float height, float inset_left=0,
			float inset_top=0, float inset_right=0, float inset_bottom=0) {

		float scale = height/mCharHeight;

		glBindTexture(GL_TEXTURE_2D,data->mTexture);
//		glBindTexture(GL_TEXTURE_2D,0);
//		glColor4f(0,0,0,0.5);
		glPushMatrix();

		//first we need to move over a little so that
		//the character has the right amount of space
		//between it and the one before it.
		//!do not need this
		//		glTranslatef(data->mBitmapGlyphLeft, 0, 0);

		//Now we move down a little in the case that the
		//bitmap extends past the bottom of the line 
		//(this is only true for characters like 'g' or 'y' and 'j'.
		glTranslatef(0, (data->mBitmapGlyphTop-data->mBitmapRows)*scale, 0);

		//Now we need to account for the fact that many of
		//our textures are filled with empty padding space.
		//We figure what portion of the texture is used by 
		//the actual character and store that information in 
		//the x and y variables, then when we draw the
		//quad, we will only reference the parts of the texture
		//that we contain the character itself.
		float x=(float)data->mBitmapWidth/(float)data->mWidth ,
				y=(float)data->mBitmapRows/ (float)data->mHeight;

		//Here we draw the texturemaped quads.
		//The bitmap that we got from FreeType was not 
		//oriented quite like we would like it to be,
		//so we need to link the texture to the quad
		//so that the result will be properly aligned.
		//ar = w/h
		//h*ar = w

		float vWidth = data->mBitmapWidth*scale;
		float vHeight = data->mBitmapRows*scale;

		if (inset_left+inset_right<vWidth && inset_top+inset_bottom<vHeight) {
			float lc = inset_left > 0 ? inset_left/vWidth : 0;
			float rc = inset_right > 0 ? inset_right/vWidth : 0;
			float tc = inset_top > 0 ? inset_top/vHeight : 0;
			float bc = inset_bottom > 0 ? inset_bottom/vHeight : 0;
			
			vWidth-=inset_left+inset_right;
			
			float height = vHeight-(inset_top+inset_bottom);
			vHeight = inset_bottom < 0 ? vHeight : vHeight-inset_bottom;
			vHeight = vHeight-inset_top;
			
			
			glTranslatef(inset_left, inset_bottom, 0);
			glBegin(GL_QUADS);
			
			glTexCoord2d(x*lc, y*tc);
			glVertex2f(0, height);
			
			glTexCoord2d(x*lc, y-(y*bc));
			glVertex2f(0, height-vHeight);
			
			glTexCoord2d(x-(x*rc), y-(y*bc));
			glVertex2f(vWidth, height-vHeight);
			
			glTexCoord2d(x-(x*rc), y*tc);
			glVertex2f(vWidth, height);
			
			glEnd();

		}

		glPopMatrix();
		glTranslatef((data->mGlyphAdvanceX >> 6)*scale, 0, 0);
	}

	inline void printLine( std::string& line,float height=1, float inset_left=0,
			float inset_top=0, float inset_right=0, float inset_bottom=0) {
		float length = 0, sHeight=0;
		float scale = height/mCharHeight;
		getDimensions(line, height, length, sHeight);
		float posx=0;
		for (unsigned int c = 0; c<line.size(); c++) {
			triCharData* data = &mCharacters[(int)line.c_str()[c]];

			float cEnd = posx + data->mBitmapWidth*scale;
			float space = length - cEnd;
			float r_ins = space >= inset_right ? 0 : inset_right-space;
			
			float t_ins = inset_top - (height-data->mBitmapRows*scale )
			+ ((data->mBitmapGlyphTop-data->mBitmapRows)*scale);
			print_char(data, height, inset_left-posx<0 ? 0 : inset_left
					-posx, t_ins<0 ? 0 : t_ins, r_ins, inset_bottom);
			posx+=(data->mGlyphAdvanceX >> 6)*scale;
		}
	}

	inline void printLines(std::vector<string>& lines,float x=0, float y=0, float height=1,
			float inset_left=0, float inset_top=0,
			float inset_right=0, float inset_bottom=0,
			tri::graphics::TColor * textColor =NULL) {
		if (textColor==NULL){
			tri::graphics::TColor c;
			c.apply();
		}else{
			textColor->apply();
		}
		float scale = height/mCharHeight;
		float posy = 0;
		for (int i=0; i<lines.size(); i++) {
			posy =(height*1.5)*i;
			string line = lines[i];
		
			glPushMatrix();
			glTranslatef(x, y-(height*1.5)*i, 0);
			float t_insA = inset_top<=posy ? 0 : inset_top-posy;
			printLine(line,height,inset_left,t_insA,inset_right,inset_bottom);
			glPopMatrix();
		}
	}

	inline void print(float x, float y, float height, const char *fmt,
			...) {
		char text[256]; // Holds Our String
		va_list ap; // Pointer To List Of Arguments

		if (fmt == NULL) { // If There's No Text
			*text=0; // Do Nothing
			return;
		} else {
			va_start(ap, fmt);
			// Parses The String For Variables
			vsprintf(text, fmt, ap); // And Converts Symbols To Actual Numbers
			va_end(ap);
			// Results Are Stored In Text
		}

		//Here is some code to split the text that we have been
		//given into a set of lines.  
		//This could be made much neater by using
		//a regular expression library such as the one avliable from
		//boost.org (I've only done it out by hand to avoid complicating
		//this tutorial with unnecessary library dependencies).
		
		std::string mText(text);
		std::vector<string> mLines;
		mLines.clear();
		std::string line = "";
		for (unsigned int i =0;i<mText.size();i++){
			char c = mText.c_str()[i];
			if(c=='\n'){
				mLines.push_back(line);
				line ="";
			}else{
				line.append(1,c);
				if(i==mText.size()-1){
					mLines.push_back(line);
				}
			}
		}
	

		printLines( mLines,x, y, height,0,0,0,0);

		//print_char(&mCharacters[100], 0.5);

	}
	
	//Get the dimensions of the image used to build
	//the given charater
	inline void getCharBitmapDimensions(char c,int &width, int& height){
		triCharData* data = &mCharacters[(int)c];
		width = data->mWidth;
		height = data->mHeight;
	}

	//private:
	std::map<std::string,triFont*> mFonts;
	int mCharHeight;
	triCharData mCharacters[128];

};

}
#endif
